/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Raytracing.Geometry;

import Raytracing.BoundingBox;
import Raytracing.Constants;
import Raytracing.Intersectable;
import Raytracing.Ray;

import Raytracing.Shaders.Shader;
import processing.core.PApplet;
import processing.core.PVector;

/**
 *
 * @author Matt
 */
public class Sphere extends Intersectable {

    public PVector loc;
    public float radius;

    public Sphere(PVector loc, float radius, Shader shader) {
        this.loc = loc;
        this.radius = radius;
        this.shader = shader;

        
        minBounds = new PVector(loc.x - radius,
                loc.y - radius,
                loc.z - radius);


        maxBounds = new PVector(loc.x + radius,
                loc.y + radius,
                loc.z + radius);

    }

    public void draw(PApplet g) {
        
        g.pushMatrix();
       // g.fill(shader.fill);
        g.sphere(radius);
        g.popMatrix();

    }

    public void draw(PApplet g, PVector camDirection) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean intersect(Ray state) {
        
        //transform to object space
       PVector origin = worldToObject(state.origin);
              
    
      
        float A = state.direction.dot(state.direction);
        
        float B = 2.f*state.direction.dot(origin);
        
        float C = origin.dot(origin) - (radius*radius);
        
        float[] hits = new float[2];
        
        if (!quadratic(A, B, C, hits)){
            return false;
        }
        
        if (hits[0] > state.maxDist || hits[1] < state.minDist){
            return false;
        }   
        state.distance = hits[0];
        if (hits[0] < state.minDist){
            state.distance = hits[1];
            if (state.distance > state.maxDist) return false;
        }
        //calc hitpoint

       PVector tmp = PVector.mult(state.direction,state.distance);

       
        state.normal = PVector.add(origin,tmp);     
        state.normal.normalize();        
        
        
        PVector n = new PVector(1.f,0,0);
        PVector m = new PVector(0,1.f,0);
        
        state.tn = state.normal.cross(n);
        if (state.tn.mag() < .01f) state.tn = state.normal.cross(m);

        state.sn = state.normal.cross(state.tn);       
        
        state.sn.normalize();
        state.tn.normalize();
   
        
        //calc UVs        
        tmp.add(origin); 
        

        float phi = (float)Math.atan2(tmp.z,tmp.x);
        if (phi < 0) phi += Constants.TWO_PI;
        state.tu = phi/Constants.TWO_PI;
        
        
        float theta = (float)(Math.acos(tmp.y/radius));
        state.tv = theta/Constants.PI;
        
        return true;
                
    }

    
    private boolean quadratic (float A, float B, float C, float[] hits){
        float discrim = B*B-4.f*A*C;
        if (discrim < 0) return false;
        float rootDiscrim = (float)Math.sqrt(discrim);
        float q;
        if (B < 0) q = -.5f*(B-rootDiscrim);
        else q = -.5f * (B+rootDiscrim);
        hits[0]=q/A;
        hits[1] = C/q;
        
        if (hits[0] > hits[1]){
            float tmp = hits[1];
            hits[1] = hits[0];
            hits[0] = tmp;
        }
        return true;
    }
    
    public PVector getPoint(float u, float v) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean inside(BoundingBox bounds) {
        return ((greaterThan(maxBounds, bounds.min)) && (lessThan(minBounds, bounds.max)));
    }

    private boolean greaterThan(PVector lhs, PVector rhs) {
        if (lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z) {
            return true;
        } else {
            return false;
        }
    }

    private boolean lessThan(PVector lhs, PVector rhs) {
        if (lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z) {
            return true;
        } else {
            return false;
        }
    }


   
    
    
    public PVector worldToObject(PVector in) {
        return PVector.sub(in,loc);
    }

    public PVector objectToWorld(PVector in) {
        return PVector.add(in,loc);
    }
}
